/**
 * 版权所有(C)，上海海鼎信息工程股份有限公司，2007，所有权利保留。
 * 
 * 项目名：	rumba
 * 文件名：	BMenu.java
 * 模块说明：	
 * 修改历史：
 * Apr 30, 2007 - lxm - 创建。
 */
package com.hd123.rumba.navigation.menu;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.hd123.rumba.cartridge.entry.Entry;
import com.hd123.rumba.exception.BusinessException;
import com.hd123.rumba.gwt.entry.client.menu.CMenuItem;
import com.hd123.rumba.log.RumbaLogger;
import com.hd123.rumba.runtime.IPersistent;
import com.hd123.rumba.runtime.biz.BBaseEntity;
import com.hd123.rumba.user.IUser;
import com.hd123.rumba.user.PE;
import com.hd123.rumba.util.ObjectUtil;

/**
 * 菜单 | 业务层.维护 | 实体
 * <p>
 * 
 * 菜单项的显示顺序遵循按照以下属性升序排序：parent + align + indexOf(this)。
 * 
 * @author lxm
 * 
 */
public class BMenu extends BBaseEntity {

  private RumbaLogger logger = RumbaLogger.getLogger(BMenu.class);

  private List<BMenuItem> items = new ArrayList();

  /** 属性：菜单项列表。只读。 */
  public List<BMenuItem> getItems() {
    return items;
  }

  public void readFrom(IPersistent source) throws BusinessException {
    if (true)
      throw BusinessException.neverCallMe();
  }

  /**
   * 添加一个菜单项，位于指定上级菜单项相同对齐方式菜单项的最后。<br>
   * 将检查菜单项的合法性，例如是否保证唯一性。
   * 
   * @param item
   *          指定被添加的菜单项。
   * @param parent
   *          指定所属上级菜单项。若传入null，表示添加的是顶级菜单项。
   * @throws BusinessException
   */
  public void addItem(BMenuItem item, BMenuItem parent) throws BusinessException {
    assert item != null;
    assert parent == null || this.equals(parent.getMenu()) : "指定的上级菜单项parent不属于当前BMenu。";
    assert item != null && !this.equals(item.getMenu()) : "指定的菜单已经属于当前BMenu。";

    verifyToAddOrInsert(item, parent);

    item.setMenu(this);
    item.setParentItem(parent);

    BMenuItem item2 = this.getFirstChild(parent);
    while (item2 != null) {
      int cmp = item2.getAlign().compareTo(item.getAlign());
      if (cmp > 0)
        break;
      item2 = this.getNextItem(item2);
    }
    if (item2 == null)
      items.add(item);
    else {
      int idx = items.indexOf(item2);
      items.add(idx, item);
    }
  }

  /**
   * 插入一个菜单项，位于指定兄弟菜单项之前，属于相同的上级菜单。
   * 
   * @param item
   *          指定被插入的菜单项。
   * @param brother
   *          指定的兄弟菜单项，不可为空。要求必须与插入的菜单项具有相同的对齐方式。
   * @throws BusinessException
   */
  public void insertItem(BMenuItem item, BMenuItem brother) throws BusinessException {
    assert item != null;
    assert brother != null;
    assert this.equals(brother.getMenu()) : "指定的兄弟菜单项brother不属于当前BMenu。";
    assert item != null && !this.equals(item.getMenu()) : "指定的菜单已经属于当前BMenu。";

    verifyToAddOrInsert(item, brother.getParentItem());

    item.setMenu(this);
    item.setParentItem(brother.getParentItem());

    int idx = items.indexOf(brother);
    if (idx < 0)
      items.add(item);
    else
      items.add(idx, item);
  }

  /**
   * 删除指定的菜单子树。
   * 
   * @param item
   *          指定被删除的菜单子树的根。若传入null，则表示删除所有菜单项。
   * @throws BusinessException
   */
  public void removeSubtree(BMenuItem item) throws BusinessException {
    List<BMenuItem> children = getDirectChildren(item);
    for (BMenuItem child : children)
      removeSubtree(child);
    item.setMenu(null);
    item.setParentItem(null);
    items.remove(item);
  }

  /**
   * 取得第一个下级菜单项。
   * 
   * @param parent
   *          指定上级菜单项，允许null，表示取得顶级菜单项。
   * @return 返回菜单项，若找不到则返回null。
   * @throws BusinessException
   */
  public BMenuItem getFirstChild(BMenuItem parent) throws BusinessException {
    BMenuItem retItem = null;
    int idx = 0;
    while (idx < items.size()) {
      BMenuItem item = items.get(idx);
      if (parent == null) {
        if (item.getParentItem() == null) {
          retItem = item;
          break;
        }
      } else {
        if (parent.equals(item.getParentItem())) {
          retItem = item;
          break;
        }
      }
      idx++;
    }
    return retItem;
  }

  /**
   * 取得最后一个下级菜单项。
   * 
   * @param parent
   *          指定上级菜单项，允许null，表示取得顶级菜单项。
   * @return 返回菜单项，若找不到则返回null。
   * @throws BusinessException
   */
  public BMenuItem getLastChild(BMenuItem parent) throws BusinessException {
    BMenuItem retItem = null;
    int idx = items.size() - 1;
    while (idx >= 0) {
      BMenuItem item = items.get(idx);
      if (parent == null) {
        if (item.getParentItem() == null) {
          retItem = item;
          break;
        }
      } else {
        if (parent.equals(item.getParentItem())) {
          retItem = item;
          break;
        }
      }
      idx--;
    }
    return retItem;
  }

  /**
   * 取得相同上级菜单项的下一个菜单项。
   * 
   * @param item
   *          指定菜单项。
   * @return 返回菜单项，若找不到则返回null。
   * @throws BusinessException
   */
  public BMenuItem getNextItem(BMenuItem item) throws BusinessException {
    assert item != null;
    assert this.equals(item.getMenu());

    BMenuItem retItem = null;
    BMenuItem parent = item.getParentItem();
    int idx = items.indexOf(item) + 1;
    while (idx < items.size()) {
      BMenuItem item2 = items.get(idx);
      if (parent == null) {
        if (item2.getParentItem() == null) {
          retItem = item2;
          break;
        }
      } else {
        if (parent.equals(item2.getParentItem())) {
          retItem = item2;
          break;
        }
      }
      idx++;
    }
    return retItem;
  }

  /**
   * 取得相同上级菜单项的上一个菜单项。
   * 
   * @param item
   *          指定菜单项。
   * @return 返回菜单项，若找不到则返回null。
   * @throws BusinessException
   */
  public BMenuItem getPriorItem(BMenuItem item) throws BusinessException {
    assert item != null;
    assert this.equals(item.getMenu());

    BMenuItem retItem = null;
    BMenuItem parent = item.getParentItem();
    int idx = items.indexOf(item) - 1;
    while (idx >= 0) {
      BMenuItem item2 = items.get(idx);
      if (parent == null) {
        if (item2.getParentItem() == null) {
          retItem = item2;
          break;
        }
      } else {
        if (parent.equals(item2.getParentItem())) {
          retItem = item2;
          break;
        }
      }
      idx--;
    }
    return retItem;
  }

  /**
   * 按照顺序取得指定菜单项的所有直接子菜单项。
   * 
   * @param item
   *          指定的菜单项。允许传入null，表示取得所有顶级菜单项。
   * @return 返回符合条件的菜单项列表，若找不到则返回空的列表。
   * @throws BusinessException
   */
  public List<BMenuItem> getDirectChildren(BMenuItem item) throws BusinessException {
    assert item == null || this.equals(item.getMenu());

    List<BMenuItem> list = new ArrayList();
    for (BMenuItem item2 : items)
      if (item == null) {
        if (item2.getParentItem() == null)
          list.add(item2);
      } else {
        if (item.equals(item2.getParentItem()))
          list.add(item2);
      }
    return list;
  }

  /**
   * 根据标题取得菜单项。
   * 
   * @param parent
   *          指定被搜索菜单项的上级菜单项，若传入null，表示搜索顶级菜单项。
   * @param caption
   *          指定菜单项的标题。
   * @return 返回符合条件的菜单项，若找不到则返回null。
   * @throws BusinessException
   */
  public BMenuItem getItemByCaption(BMenuItem parent, String caption) throws BusinessException {
    assert parent == null || this.equals(parent.getMenu());

    BMenuItem retItem = null;
    BMenuItem item = this.getFirstChild(parent);
    while (item != null) {
      if (item.getCaption().equals(caption)) {
        retItem = item;
        break;
      }
      item = this.getNextItem(item);
    }
    return retItem;
  }

  /**
   * 根据唯一标识查找菜单项。
   * 
   * @param id
   *          指定查找菜单项的唯一标识。
   * @return 返回找到的菜单项，若找不到则返回null。
   * @throws BusinessException
   */
  public BMenuItem getItemById(String id) throws BusinessException {
    assert id != null;
    BMenuItem retItem = null;
    for (BMenuItem item : this.items)
      if (item.getId().equals(id)) {
        retItem = item;
        break;
      }
    return retItem;
  }

  /**
   * 判断指定菜单项是否具有下级菜单项。
   * 
   * @param item
   *          指定菜单项。
   * @return true | false。
   * @throws BusinessException
   */
  public boolean hasChildren(BMenuItem item) throws BusinessException {
    assert item != null;
    assert this.equals(item.getMenu());

    BMenuItem item2 = this.getFirstChild(item);
    return item2 != null;
  }

  /**
   * 将指定的菜单合并到当前菜单中。
   * 
   * @param menu
   *          指定被合并的菜单。
   * @throws BusinessException
   */
  public void merge(BMenu menu) throws BusinessException {
    assert menu != null;
    assert !menu.equals(this);
    HashMap<BMenuItem, BMenuItem> map = new HashMap();
    mergeSubtree(menu, null, map);
  }

  /**
   * 根据指定用户的权限将当前不可见的菜单项删除，以得到该用户可见的菜单。
   * 
   * @param user
   *          指定用户。
   * @throws BusinessException
   */
  public void obscurByUser(IUser user) throws BusinessException {
    assert user != null;

    List<BMenuItem> list = new ArrayList();
    BMenuItem item = getFirstChild(null);
    while (item != null) {
      if (obscurItemByUser(user, item))
        list.add(item);
      item = getNextItem(item);
    }

    for (BMenuItem item2 : list)
      removeSubtree(item2);
  }

  /**
   * 对指定菜单项的标题进行合法性检查。
   * 
   * @param item
   * @param parent
   * @throws BusinessException
   */
  void validateItemCaption(BMenuItem item, BMenuItem parent) throws BusinessException {
    assert parent == null || this.equals(parent.getMenu());

    if (item.getCaption() == null && "".equals(item.getCaption().trim()))
      throw new BusinessException(CMenuItem.CAPTION_CLASS + "的" + CMenuItem.CAPTION_CAPTION
          + "不可为空。");
    BMenuItem item2 = this.getItemByCaption(parent, item.getCaption());
    if (item2 != null)
      throw new BusinessException("已经存在相同" + CMenuItem.CAPTION_CAPTION + "的"
          + CMenuItem.CAPTION_CLASS + "。");
  }

  /** 添加一个菜单项前的检查。 */
  private void verifyToAddOrInsert(BMenuItem item, BMenuItem parent) throws BusinessException {
    assert !this.equals(item.getMenu()) : "被添加的BMenuItem已经属于当前BMenu对象。";

    validateItemCaption(item, parent);

    checkToResetId(item);

    if (item.getAlign() == null)
      item.setAlign(CMenuItem.VALUE_ALIGN_NORMAL);
  }

  /** 检查指定菜单项的id，若重复或为null的情况下则自动产生一个。 */
  private void checkToResetId(BMenuItem item) throws BusinessException {
    assert !this.equals(item.getMenu());
    if (item.getId() == null)
      item.setId("menuitem");
    String prefix = item.getId();
    String newId = prefix;
    BMenuItem item2 = this.getItemById(newId);
    if (item2 == null)
      return;
    int idx = 0;
    while (item2 != null) {
      idx++;
      newId = prefix + "_" + idx;
      item2 = this.getItemById(newId);
    }
    item.setId(newId);
  }

  private void mergeSubtree(BMenu menu, BMenuItem parent, HashMap<BMenuItem, BMenuItem> map)
      throws BusinessException {
    assert menu != null;
    assert parent == null || menu.equals(parent.getMenu());

    BMenuItem item = menu.getFirstChild(parent);
    BMenuItem localParent = map.get(parent);
    while (item != null) {
      Entry entry = item.getEntry();

      // 根据菜单项合并规则，在本地查找可能存在的相同的菜单项放在localItem中。
      // 菜单项以下属性相同进行合并：parentItem, caption, entry
      BMenuItem localItem = null;
      BMenuItem localItem2 = getItemByCaption(localParent, item.getCaption());
      if (localItem2 != null) {
        Entry localEntry = localItem2.getEntry();
        if (ObjectUtil.equals(entry, localEntry))
          localItem = localItem2;
      }

      if (localItem == null) {
        localItem = new BMenuItem();
        localItem.copyValues(item);
        if (localItem2 != null) {
          generateNewCaption(localItem, localParent);
          logger.warn("重新命名菜单项标题为：" + localItem.getCaption());
        }
        this.addItem(localItem, localParent);
      } else
        localItem.copyValues(item);
      map.put(item, localItem);

      mergeSubtree(menu, item, map);
      item = menu.getNextItem(item);
    }
  }

  private void generateNewCaption(BMenuItem item, BMenuItem parent) throws BusinessException {
    assert item != null;
    assert item.getCaption() != null;
    int i = 1;
    String caption = item.getCaption() + "_" + i;
    while (getItemByCaption(parent, caption) != null) {
      i++;
      caption = item.getCaption() + "_" + i;
    }
    item.setCaption(caption);
  }

  /** 判断是否需要屏蔽指定菜单项 */
  private boolean obscurItemByUser(IUser user, BMenuItem item) throws BusinessException {
    assert item != null;
    assert this.equals(item.getMenu());

    // 如果当前菜单项需要检查权限，则进行检查。
    // 如果检查结果为没有权限，则直接返回需要屏蔽。
    if (item.getEntry() != null) {
      PE pe = item.getEntry().getPermissions();
      if (pe != null && !user.hasPermissions(pe))
        return true;
    }

    if (!hasChildren(item))
      return false;

    // 遍历所有下一级子菜单项，找出需要被屏蔽的放入list。
    boolean obscur = true;
    List<BMenuItem> list = new ArrayList();
    BMenuItem child = getFirstChild(item);
    while (child != null) {
      if (obscurItemByUser(user, child))
        list.add(child);
      else
        obscur = false;
      child = getNextItem(child);
    }

    // 删除list中所有子菜单项。
    for (BMenuItem item2 : list)
      removeSubtree(item2);

    return obscur;
  }

}
